package cn.jkgly.websocket.server;

import cn.jkgly.test.pojo.JSONResult;
import cn.jkgly.websocket.entity.Client;
import cn.jkgly.websocket.entity.Message;
import cn.jkgly.websocket.entity.SendMessageResponse;
import cn.jkgly.websocket.entity.WebSocketTranType;
import cn.jkgly.wxUser.entity.WxRelateAccount;
import cn.jkgly.wxUser.service.IWxRelateAccountService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.apache.commons.net.ntp.TimeStamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@ServerEndpoint(value = "/socketServer/{userName}")
@Component
public class SocketServer {

    static IWxRelateAccountService wxRelateAccountService;

    private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);

    /**
     * 用线程安全的CopyOnWriteArraySet来存放客户端连接的信息
     */
    private static CopyOnWriteArraySet<Client> socketServers = new CopyOnWriteArraySet<>();

    /**
     * websocket封装的session,信息推送，就是通过它来信息推送
     */
    private Session session;

    /**
     * 服务端的userName,因为用的是set，每个客户端的username必须不一样，否则会被覆盖。
     * 要想完成ui界面聊天的功能，服务端也需要作为客户端来接收后台推送用户发送的信息
     */
    private final static String SYS_USERNAME = "niezhiliang9595";


    @Autowired
    public void setWxRelateAccountService(IWxRelateAccountService wxRelateAccountService) {
        SocketServer.wxRelateAccountService = wxRelateAccountService;
    }

    /**
     * 用户连接时触发，我们将其添加到
     * 保存客户端连接信息的socketServers中
     *
     * @param session
     * @param userName
     */
    @OnOpen
    public void open(Session session, @PathParam(value = "userName") String userName/*, @PathParam(value = "type") String type*/) {
        this.session = session;
        //  addClient(userName, type);
        addClient(userName);
        //socketServers.add(new Client(userName, session, type));
        socketServers.add(new Client(userName, session));
        logger.info("Client:【{}】Successful connection", userName);
        wxRelateAccountService.addLog("连接成功","客户端连接成功",userName);

    }

    /**
     * 收到客户端发送信息时触发
     * 我们将其推送给客户端(niezhiliang9595)
     * 其实也就是服务端本身，为了达到前端聊天效果才这么做的
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {


        Client client = socketServers.stream().filter(cli -> cli.getSession() == session).collect(Collectors.toList()).get(0);
        System.out.println(message);
        JSONObject jsonObject = JSON.parseObject(message);


        Message msg = jsonObject.toJavaObject(Message.class);
        if (!WebSocketTranType.HEART_BEAT.equals(msg.getType())) {
            logger.info("Client:【{}】send message:{}", client.getUserName(), message);
            wxRelateAccountService.addLog(message,"客户端发送消息",client.getUserName());
        }
        msg.setFromUser(client.getUserName());
        msg.setTimeStamp(System.currentTimeMillis());
        String msgString = new JSONObject().toJSONString(msg);
        if (WebSocketTranType.COMM.equals(msg.getType())) {
            sendData(client.getUserName(), msgString, msg.getToUser());
        }
        //授权PC电脑端登陆
        if (WebSocketTranType.AUTH.equals(msg.getType())) {
            getWxRelateAccount(client.getUserName(), msg.getAppName(), msg.getToUser());
        }

        //接收心跳包并返回
        if (WebSocketTranType.HEART_BEAT.equals(msg.getType())) {
            //收到心跳包返回给自己
            sendData(client.getUserName(), msgString, client.getUserName());
        }
        //发送lis化验结果
        if (WebSocketTranType.LIS_HY.equals(msg.getType())) {
            sendData(client.getUserName(), msgString, msg.getToUser());
        }
        //发送pacs检查通知
        if (WebSocketTranType.PACS_JC.equals(msg.getType())) {
            sendData(client.getUserName(), msgString, msg.getToUser());
        }
        //发送his医保码
        if (WebSocketTranType.HIS_YB.equals(msg.getType())) {
            sendData(client.getUserName(), msgString, msg.getToUser());
        }

        //手机端检测
        if (WebSocketTranType.MOBILE_TEST.equals(msg.getType())) {
            sendData(client.getUserName(), msgString, msg.getToUser());
        }
        //查询服务器时间返回
        if(WebSocketTranType.TIME.equals(msg.getType())){
            NTPUDPClient timeClient = new NTPUDPClient();
            String timeServerUrl = "time1.aliyun.com";
            InetAddress timeServerAddress = null;
            try {
                timeServerAddress = InetAddress.getByName(timeServerUrl);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            TimeInfo timeInfo = null;
            try {
                timeInfo = timeClient.getTime(timeServerAddress);
            } catch (IOException e) {
                e.printStackTrace();
            }
            TimeStamp timeStamp = timeInfo.getMessage().getTransmitTimeStamp();
            Date date = timeStamp.getDate();
            logger.info(date.toString());
            msg.setMessage(Long.valueOf(date.getTime()).toString());
            String TimeMsg = new JSONObject().toJSONString(msg);
            sendData(client.getUserName(), TimeMsg, msg.getToUser());
        }
    }

    /**
     * 连接关闭触发，通过sessionId来移除
     * socketServers中客户端连接信息
     */
    @OnClose
    public void onClose() {
        socketServers.forEach(client -> {
            if (client.getSession().getId().equals(session.getId())) {
                socketServers.remove(client);
                logger.info("Client:【{}】 connection closed", client.getUserName());
                wxRelateAccountService.addLog("客户端断开","客户端断开",client.getUserName());

            }
        });
    }

    /**
     * 发生错误时触发
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error) {
        socketServers.forEach(client -> {
            if (client.getSession().getId().equals(session.getId())) {
                socketServers.remove(client);
                logger.error("Client:【{}】 Exception", client.getUserName());
                wxRelateAccountService.addLog(error.toString(),"客户端异常",client.getUserName());

                error.printStackTrace();
            }
        });
        
    }

    /**
     * 信息发送的方法，通过客户端的userName
     * 拿到其对应的session，调用信息推送的方法
     *
     * @param message
     * @param userName
     */
    public synchronized static boolean sendMessage(String message, String userName) {

        AtomicBoolean result = new AtomicBoolean(false);
        socketServers.forEach(client -> {
            if (userName.equals(client.getUserName())) {
                client.getSession().getAsyncRemote().sendText(message);
                logger.info("Server push to client :【{}】", client.getUserName(), message);
                wxRelateAccountService.addLog(message,"服务端转发消息给客户端",client.getUserName());

                result.set(true);
            }
        });
        if(!result.get()){
                logger.info("Client:【{}】 sendMessage failed",userName);
            wxRelateAccountService.addLog(message,"客户端发送消息失败",userName);

        }
        return result.get();
    }

    /**
     * 获取服务端当前客户端的连接数量，
     * 因为服务端本身也作为客户端接受信息，
     * 所以连接总数还要减去服务端
     * 本身的一个连接数
     * <p>
     * 这里运用三元运算符是因为客户端第一次在加载的时候
     * 客户端本身也没有进行连接，-1 就会出现总数为-1的情况，
     * 这里主要就是为了避免出现连接数为-1的情况
     *
     * @return
     */
    public synchronized static int getOnlineNum() {
        return socketServers.stream().filter(client -> !client.getUserName().equals(SYS_USERNAME)).collect(Collectors.toList()).size();
    }

    /**
     * 获取在线用户名，前端界面需要用到
     *
     * @return
     */
    public synchronized static List<String> getOnlineUsers() {

        List<String> onlineUsers = socketServers.stream().filter(client -> !client.getUserName().equals(SYS_USERNAME)).map(client -> client.getUserName()).collect(Collectors.toList());

        return onlineUsers;
    }

    /**
     * 信息群发，我们要排除服务端自己不接收到推送信息
     * 所以我们在发送的时候将服务端排除掉
     *
     * @param message
     */
    public synchronized static void sendAll(String message) {
        //群发，不能发送给服务端自己
        socketServers.stream().filter(cli -> cli.getUserName() != SYS_USERNAME).forEach(client -> {
            try {
                client.getSession().getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        logger.info("Server push all Client :【{}】", message);
        wxRelateAccountService.addLog(message,"服务的转发消息给所有客户端","all");
    }

    /**
     * 多个人发送给指定的几个用户
     *
     * @param message
     * @param persons
     */
    public synchronized static void SendMany(String message, String[] persons) {
        for (String userName : persons) {
            sendMessage(message, userName);
        }
    }

    /**
     * 客户端连接之前先查询用户名是否已经使用
     * 如果已经连接先删除在连接防止一个用户名拥有多个客户端
     *
     * @param userName
     */
    public synchronized static boolean addClient(String userName/*, String type*/) {

        AtomicBoolean result = new AtomicBoolean(false);
        if (socketServers.size() != 0) {
            socketServers.forEach(client -> {
                if (userName.equals(client.getUserName())/* && type.equals(client.getType())*/) {
                    socketServers.remove(client);
                    result.set(true);
                }
            });
        }


        return result.get();
    }

    /**
     * 根据openid和appName 获取需授权平台的用户名和密码并发送授权指令
     *
     * @param openid·
     * @param appName
     * @param toUser
     */
    public synchronized static void getWxRelateAccount(String openid, String appName, String toUser) {
        QueryWrapper<WxRelateAccount> queryWrapper = new QueryWrapper<WxRelateAccount>();
        queryWrapper.eq("openid", openid).eq("app_name", appName);
        WxRelateAccount relateAccount = wxRelateAccountService.getOne(queryWrapper);
        String msgString = new JSONObject().toJSONString(relateAccount);

        System.out.println(msgString);
        if (relateAccount == null) {
            logger.info("【{}】:failed", openid);
            wxRelateAccountService.addLog("给用户没有关联账号","授权失败",openid);
            String msgString1 = new JSONObject().toJSONString(JSONResult.errorException("fail"));
            sendMessage(msgString1, toUser);
        } else {
            logger.info("【{}】:success", openid);
            wxRelateAccountService.addLog(msgString,"授权成功",openid);
            sendMessage(msgString, toUser);
            String msgString1 = new JSONObject().toJSONString(JSONResult.ok("success"));
            sendMessage(msgString1, openid);

        }
    }

    /**
     * 转发客户端发送给其他客户端的消息
     *
     * @param fromUser·
     * @param message
     * @param toUser
     */
    public synchronized static void sendData(String fromUser, String message, String toUser) {
        if (!fromUser.equals(toUser)) {
            JSONObject jsonObject = JSON.parseObject(message);
            Message msg = jsonObject.toJavaObject(Message.class);
            if (!sendMessage(message, toUser)) {
                msg.setType(WebSocketTranType.FAILED);
                msg.setMessage("用户不在线");
            } else {
                msg.setType(WebSocketTranType.SUCCESS);
                msg.setMessage("发送成功");
            }
            sendMessage(new JSONObject().toJSONString(msg), fromUser);
        }else{
            sendMessage(message, fromUser);
        }
    }
}
